package redis

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"fmt"
	"time"

	"github.com/go-redis/redis"

	"leeframe/configs"
)

var redisClient *redis.Client

// InitRedis 初始化Redis
func InitRedis() error {
	configRedis := configs.Get().Redis
	redisClient = redis.NewClient(&redis.Options{
		Addr:        configRedis.Host,
		Password:    configRedis.Password,
		DB:          configRedis.RedisDB,
		IdleTimeout: configRedis.IdleTimeout,
	})

	_, err := redisClient.Ping().Result()
	if err != nil {
		return err
	}

	return nil
}

// Close 关闭Redis
func Close() error {
	return redisClient.Close()
}

// GetRedis get redis client
func GetRedis() *redis.Client {
	return redisClient
}

// Ping ping redis
func Ping() *redis.StatusCmd {
	return redisClient.Ping()
}

// 获取完整Key
func wrapperKey(key string) string {
	prefix := configs.Get().Redis.RedisPrefix
	return fmt.Sprintf("%s%s", prefix, key)
}

// Set 设置缓存
func Set(key string, value interface{}, timeout int) error {
	data, err := encode(value)
	if err != nil {
		return err
	}
	timeoutDuration := time.Duration(timeout) * time.Second
	if timeout == 0 {
		durationCmd := redisClient.TTL(wrapperKey(key))
		if durationCmd.Err() == nil {
			timeoutDuration = durationCmd.Val()
		}
	}
	return redisClient.Set(wrapperKey(key), data, timeoutDuration).Err()
}

// IncrBy 原子性增加
func IncrBy(key string, value int64, timeout int) (int64, error) {
	var incr *redis.IntCmd
	_, err := redisClient.Pipelined(func(pipe redis.Pipeliner) error {
		incr = pipe.IncrBy(wrapperKey(key), value)
		if timeout > 0 {
			timeoutDuration := time.Duration(timeout) * time.Second
			if Exists(key) {
				durationCmd := redisClient.TTL(wrapperKey(key))
				if durationCmd.Err() == nil {
					timeoutDuration = durationCmd.Val()
				}
			}
			pipe.Expire(wrapperKey(key), timeoutDuration)
		}
		return nil
	})
	rate, _ := incr.Result()
	return rate, err
}

// Exists 判断key是否存在
func Exists(key string) bool {
	data, err := redisClient.Exists(wrapperKey(key)).Result()
	if err != nil {
		return false
	}
	if data == 1 {
		return true
	}
	return false
}

// Get 获取缓存
func Get(key string, value interface{}) error {
	data, err := redisClient.Get(wrapperKey(key)).Bytes()
	if err != nil {
		return err
	}
	return decode(data, value)
}

// Del 删除缓存
func Del(key string) error {
	return redisClient.Del(wrapperKey(key)).Err()
}

// GetTimeOut 获取key过期时间
func GetTimeOut(key string) (timeout time.Duration) {
	durationCmd := redisClient.TTL(key)
	if durationCmd.Err() == nil {
		timeout = durationCmd.Val()
	}
	return
}

// GeoAdd 添加经纬度
func GeoAdd(key, name string, longitude, latitude float64) error {
	return redisClient.GeoAdd(key, &redis.GeoLocation{
		Name:      name,
		Longitude: longitude,
		Latitude:  latitude,
	}).Err()
}

// GeoRem 删除
func GeoRem(key string, names ...interface{}) error {
	return redisClient.ZRem(wrapperKey(key), names...).Err()
}

// GeoAdds 添加多个经纬度
func GeoAdds(key string, list ...*redis.GeoLocation) error {
	return redisClient.GeoAdd(wrapperKey(key), list...).Err()
}

// GeoCount 获取数量
func GeoCount(key string) (int64, error) {
	data, err := redisClient.ZCard(wrapperKey(key)).Result()
	if err != nil {
		return 0, err
	}
	return data, nil
}

// GeoRadius 查找附近
func GeoRadius(key string, longitude, latitude float64) ([]*GeoResponse, error) {
	resRadiu, err := redisClient.GeoRadius(wrapperKey(key), longitude, latitude, &redis.GeoRadiusQuery{
		Radius:   10000,
		Unit:     "m",
		WithDist: true,
		Sort:     "ASC",
	}).Result()
	if err != nil {
		return nil, err
	}
	var geoResponse []*GeoResponse
	for _, v := range resRadiu {
		geoResponse = append(geoResponse, &GeoResponse{v.Name, v.Dist})
	}
	return geoResponse, nil
}

// GeoDist 查询两个位置之间的距离
func GeoDist(key, address1, address2 string) (float64, error) {
	return redisClient.GeoDist(wrapperKey(key), address1, address2, "m").Result()
}

type GeoResponse struct {
	ID   string
	Dist float64
}

// encode 数据编码
func encode(data interface{}) ([]byte, error) {
	if b, err := json.Marshal(data); err == nil {
		return []byte(b), nil
	}
	buf := bytes.NewBuffer(nil)
	enc := gob.NewEncoder(buf)
	err := enc.Encode(data)
	if err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

// decode 数据解码
func decode(data []byte, to interface{}) error {
	if err := json.Unmarshal(data, to); err == nil {
		return nil
	}
	buf := bytes.NewBuffer(data)
	dec := gob.NewDecoder(buf)
	return dec.Decode(to)
}
